home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / www / htmlweb < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  47.8 KB  |  1,241 lines

  1. #!/usr/local/bin/gawk -f
  2. #!/usr/bin/awk -f
  3. # @(#) htmlweb.gawk 2.0 96/12/14
  4. # 94/02/24 John H. DuBois III (john@armory.com)
  5. # 94/03/07 Fixed path generation
  6. # 94/03/09 Use gawk so - options can be given
  7. # 94/03/11 href file might not be surrounded by quotes
  8. # 94/03/13 Added options.
  9. # 94/03/18 Ignore non-local links, and the internal-name part of links.
  10. # 94/06/28 Ignore absolute path links.
  11. # 96/12/14 Added k option.  Rewrote to use DrawTrees lib.
  12.  
  13. BEGIN {
  14.     Name = "htmlweb"
  15.     rcFile = "." Name
  16.     nIndent = 4
  17.     # added: icSaAn
  18.     Usage = \
  19. "Usage: " Name " [-hesaAn] [-k<type-list>] [-i<indent>] [-c<columns>]\n"\
  20. "       [html-file]"
  21.     ARGC = Opts(Name,Usage,"i>c<saAhexk;",0,"~/" rcFile ":$HOME/" rcFile,
  22.     "INDENT,COLUMNS,SPACES,ASCII,NOARROW", 0,"n",0,"","1,w")
  23.     if ("h" in Options) {
  24.     printf \
  25. "%s: Print HTML links in hierarchical format.\n"\
  26. "%s\n"\
  27. "%s follows and prints all href links found in an HTML file, processing\n"\
  28. "each file found recursively.\n"\
  29. "If no filename is given, index.html is used.\n"\
  30. "Legend for files:\n"\
  31. "+: Already saw this link.\n"\
  32. "!: Bad link (file does not exist or can't be read).\n"\
  33. "@: Possibly non-local link; not checked.\n"\
  34. "#: Absolute path in link; not checked.\n"\
  35. "Note: This utility is fragile & easily confused.\n"\
  36. "Options:\n"\
  37. "Some of the following options can also be set by assigning values to\n"\
  38. "variables in a configuration file named %s, which is searched for in the\n"\
  39. "invoking user's home directory and in the directory specified by the\n"\
  40. "environment variable UHOME, if it is set (if both files exist, values set\n"\
  41. "in the former take precedence).  Variables are assigned to with the\n"\
  42. "syntax:  varname=value  or in the case of flags, by simply putting the\n"\
  43. "indicated variable name in the file without a value.  Variable names are\n"\
  44. "given in parentheses in the option descriptions.\n"\
  45. "-h: Print this help.\n"\
  46. "-k<type-list>: Ignore (do not print) links whose type is given in\n"\
  47. "    <type-list>.  The types are the characters given above (+!@#).\n"\
  48. "-e: Print errors (bad links) only.\n"\
  49. "-n: Do not read the configuration file.\n"\
  50. "The following options control the manner in which the tree is drawn:\n"\
  51. "-t: Prefix each directory name with its index in the tree being printed.\n"\
  52. "-i<indent>: The number of character positions to indent when showing the\n"\
  53. "    references found in a file.  The minimum and default is %d. (INDENT)\n"\
  54. "-c<columns>: The display width to use.  Output is truncated to <columns>\n"\
  55. "    columns.  The default is to use one fewer than the width of the user's\n"\
  56. "    terminal. If -C0 is given, the output is not truncated.  (COLUMNS)\n"\
  57. "-a: Normally, the tree is drawn using box-drawing character appropriate to\n"\
  58. "    the type of terminal the program is invoked from.  If the terminal\n"\
  59. "    does not have box-drawing characters available or -a is given, the\n"\
  60. "    tree is drawn using ASCII characters.  (ASCII)\n"\
  61. "-s: Draw the tree using nothing but spaces for indentation.  (SPACES)\n"\
  62. "-A: Do not put an arrow to the left of each directory name.  (NOARROW)\n",
  63.     Name,Usage,Name,rcFile,nIndent
  64.     exit(0)
  65.     }
  66.     SUBSEP = ","    # for debugging
  67.     Spaces = "s" in Options
  68.     if ("a" in Options)
  69.     delete ENVIRON["TERM"]
  70.     if (ARGC <= 1) {
  71.     ARGV[1] = "index.html"
  72.     ARGC = 2
  73.     }
  74.     ErrorsOnly = "e" in Options
  75.     hrefPat = " href="
  76.     hrefPatLen = length(hrefPat)
  77.     if ("i" in Options)
  78.     nIndent = Options["i"]
  79.     Debug = "x" in Options
  80.     if ("k" in Options) {
  81.     tm = Options["k"]
  82.     len = length(tm)
  83.     for (i = 1; i <= len; i++)
  84.         Skip[substr(tm,i,1)]
  85.     }
  86.     if ("c" in Options)
  87.         maxLength = Options["c"]
  88.     else {
  89.     maxLength = tiget1("cols")
  90.     maxLength = (maxLength == "") ? 79 : (maxLength - 1)
  91.     }
  92.     useArrow = !("A" in Options)
  93.     for (i = 1; i < ARGC; i++)
  94.     childData[i] = ""
  95.     split("",emptyArr)    # make awk know this is an array
  96.     bfBuildTree(Tree,ARGV,childData,emptyArr,1)
  97.     if (!ErrorsOnly)
  98.     DrawTrees(Tree,nIndent,0,altChars,Spaces,"",useArrow,maxLength,0,0)
  99. }
  100.  
  101. # Find all href links in File and put them in cTreeData[].
  102. # Globals: LinksFound[] is used to track visited links.
  103. # Dir is the directory the file path is relative to, for use if File isn't an
  104. # absolute path.
  105. # Returns the string to be printed for File
  106. function getChildren(Files,Dir,cTreeData,cChildData,i,Depth,startVal,
  107. ret,Line,Ref,FullPath,hEnd,childNum,File) {
  108.     File = Files[i]
  109.     if (File ~ "^/")
  110.     FullPath = File
  111.     else
  112.     FullPath = Dir File
  113.     if (Debug) {
  114.     print "Dir: " Dir
  115.     printf "Processing html file: %s\n",File
  116.     }
  117.     # make this work whether called by buildTree() or bfBuildTree()
  118.     childNum = startVal ? startVal - 1 : 0
  119.     while ((ret = (getline Line < FullPath)) == 1) {
  120.     while (Line ~ hrefPat) {
  121.         if ((Line = substr(Line,index(Line,hrefPat)+hrefPatLen)) == "") {
  122.         while ((ret = (getline Line < FullPath)) == 1 && \
  123.         Line ~ "^[ \t]*$")
  124.             ;
  125.         if (ret != 1)
  126.             break
  127.         }
  128.         # If no > found on this line, assume entire line is link
  129.         # (presumably the > is on the next line)
  130.         if (hEnd = index(Line,">"))
  131.         Ref = substr(Line,1,hEnd-1)
  132.         else
  133.         Ref = Line
  134.         Line = substr(Line,length(Ref)+1)
  135.         # Refs might or might not be surrounded by quotes
  136.         sub("^\"*","",Ref)
  137.         sub("\"*$","",Ref)
  138.         sub("^[ \t]+","",Ref)    # discard leading whitespace
  139.         sub("[ \t]+$","",Ref)    # discard trailing whitespace
  140.         sub("#.*","",Ref)        # Don't bother checking internal names
  141.         if (Ref == "") {    # null ref
  142.         if (ErrorsOnly)
  143.             print "Null reference"
  144.         cTreeData[++childNum] = "!**NULL**"
  145.         }
  146.         else if (Ref ~ ":|^//") {    # remote link
  147.         if (!("@" in Skip))
  148.             cTreeData[++childNum] = "@" Ref
  149.         }
  150.         else if (Ref in LinksFound) {    # already seen
  151.         if (!ErrorsOnly && !("+" in Skip))
  152.             cTreeData[++childNum] = "+" Ref
  153.         }
  154.         else if (Ref ~ "^/") {    # absolute path
  155.         if (!("#" in Skip))
  156.             cTreeData[++childNum] = "#" Ref
  157.         }
  158.         else {
  159.         LinksFound[Ref]
  160.         if (Debug)
  161.             printf "Following link: <%s>\n",Ref
  162.         Dir = FullPath
  163.         sub("[^/]+$","",Dir)
  164.         gsub("[^/]*/\\.\\./","",Dir)
  165.         cTreeData[++childNum] = Ref
  166.         cChildData[childNum] = Dir
  167.         }
  168.     }
  169.     }
  170.     close(FullPath)
  171.     if (ret) {
  172.     if (ErrorsOnly)
  173.         print "!" FullPath
  174.     if (("!" in Skip))
  175.         delete Files[i]
  176.     else
  177.         Files[i] = "!" File
  178.     }
  179.     childNum
  180.     return startVal ? (childNum - startVal + 1) : childNum
  181. }
  182.  
  183. ### Start of ProcArgs library
  184. # @(#) ProcArgs 1.11 96/12/08
  185. # 92/02/29 john h. dubois iii (john@armory.com)
  186. # 93/07/18 Added "#" arg type
  187. # 93/09/26 Do not count -h against MinArgs
  188. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  189. #          Removed meaning of "+" or "-" by itself.
  190. # 94/03/08 Added & option and *()< option types.
  191. # 94/04/02 Added NoRCopt to Opts()
  192. # 94/06/11 Mark numeric variables as such.
  193. # 94/07/08 Opts(): Do not require any args if h option is given.
  194. # 95/01/22 Record options given more than once.  Record option num in argv.
  195. # 95/06/08 Added ExclusiveOptions().
  196. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  197. #          Expand $VARNAME at the start of its filenames.
  198. #          Let varname=0 and -option- turn off an option.
  199. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  200. #          of the vars should be searched for in the environment.
  201. #          Check for duplicate rcfiles.
  202. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  203. #          now return various negatives values on error, not just -1, and
  204. #          Opts() may set Err to various positive values, not just 1.
  205. #          Added AllowUnrecOpt.
  206. # 96/05/23 Check type given for & option
  207. # 96/06/15 Re-port to awk
  208. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  209. #          used by other functions.
  210. # 96/10/15 Added OptChars
  211. # 96/11/01 Added exOpts arg to Opts()
  212. # 96/11/16 Added ; type
  213. # 96/12/08 Added Opt2Set() & Opt2Sets()
  214. # 96/12/27 Added CmdLineOpt()
  215.  
  216. # optlist is a string which contains all of the possible command line options.
  217. # A character followed by certain characters indicates that the option takes
  218. # an argument, with type as follows:
  219. # :    String argument
  220. # ;    Non-empty string argument
  221. # *    Floating point argument
  222. # (    Non-negative floating point argument
  223. # )    Positive floating point argument
  224. # #    Integer argument
  225. # <    Non-negative integer argument
  226. # >    Positive integer argument
  227. # The only difference the type of argument makes is in the runtime argument
  228. # error checking that is done.
  229.  
  230. # The & option is a special case used to get numeric options without the
  231. # user having to give an option character.  It is shorthand for [-+.0-9].
  232. # If & is included in optlist and an option string that begins with one of
  233. # these characters is seen, the value given to "&" will include the first
  234. # char of the option.  & must be followed by a type character other than ":"
  235. # or ";".
  236. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  237.  
  238. # Strings in argv[] which begin with "-" or "+" are taken to be
  239. # strings of options, except that a string which consists solely of "-"
  240. # or "+" is taken to be a non-option string; like other non-option strings,
  241. # it stops the scanning of argv and is left in argv[].
  242. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  243. # If an option takes an argument, the argument may either immediately
  244. # follow it or be given separately.
  245. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  246. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  247. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  248. # this feature had a flaw that caused problems in some cases.  See the OptChars
  249. # parameter to explicitly set the option-specifier characters.
  250.  
  251. # If an option that does not take an argument is given,
  252. # an index with its name is created in Options and its value is set to the
  253. # number of times it occurs in argv[].
  254.  
  255. # If an option that does take an argument is given, an index with its name is
  256. # created in Options and its value is set to the value of the argument given
  257. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  258. # If an option that takes an argument is given more than once,
  259. # Options[option-name,"count"] is incremented, and the value is assigned to
  260. # the index (option-name,instance) where instance is 2 for the second occurance
  261. # of the option, etc.
  262. # In other words, the first time an option with a value is encountered, the
  263. # value is assigned to an index consisting only of its name; for any further
  264. # occurances of the option, the value index has an extra (count) dimension.
  265.  
  266. # The sequence number for each option found in argv[] is stored in
  267. # Options[option-name,"num",instance], where instance is 1 for the first
  268. # occurance of the option, etc.  The sequence number starts at 1 and is
  269. # incremented for each option, both those that have a value and those that
  270. # do not.  Options set from a config file have a value of 0 assigned to this.
  271.  
  272. # Options and their arguments are deleted from argv.
  273. # Note that this means that there may be gaps left in the indices of argv[].
  274. # If compress is nonzero, argv[] is packed by moving its elements so that
  275. # they have contiguous integer indices starting with 0.
  276. # Option processing will stop with the first unrecognized option, just as
  277. # though -- was given except that unlike -- the unrecognized option will not be
  278. # removed from ARGV[].  Normally, an error value is returned in this case.
  279. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  280. # be found, so the number of remaining arguments is returned instead.
  281. # If OptChars is not a null string, it is the set of characters that indicate
  282. # that an argument is an option string if the string begins with one of the
  283. # characters.  A string consisting solely of two of the same option-indicator
  284. # characters stops the scanning of argv[].  The default is "-+".
  285. # argv[0] is not examined.
  286. # The number of arguments left in argc is returned.
  287. # If an error occurs, the global string OptErr is set to an error message
  288. # and a negative value is returned.
  289. # Current error values:
  290. # -1: option that required an argument did not get it.
  291. # -2: argument of incorrect type supplied for an option.
  292. # -3: unrecognized (invalid) option.
  293. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  294. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  295. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  296. {
  297. # ArgNum is the index of the argument being processed.
  298. # ArgsLeft is the number of arguments left in argv.
  299. # Arg is the argument being processed.
  300. # ArgLen is the length of the argument being processed.
  301. # ArgInd is the position of the character in Arg being processed.
  302. # Option is the character in Arg being processed.
  303. # Pos is the position in OptList of the option being processed.
  304. # NumOpt is true if a numeric option may be given.
  305.     ArgsLeft = argc
  306.     NumOpt = index(OptList,"&")
  307.     OptionNum = 0
  308.     if (OptChars == "")
  309.     OptChars = "-+"
  310.     while (OptChars != "") {
  311.     c = substr(OptChars,1,1)
  312.     OptChars = substr(OptChars,2)
  313.     OptCharSet[c]
  314.     OptTerm[c c]
  315.     }
  316.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  317.     Arg = argv[ArgNum]
  318.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  319.         break    # Not an option; quit
  320.     if (Arg in OptTerm) {
  321.         delete argv[ArgNum]
  322.         ArgsLeft--
  323.         break
  324.     }
  325.     ArgLen = length(Arg)
  326.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  327.         Option = substr(Arg,ArgInd,1)
  328.         if (NumOpt && Option ~ /[-+.0-9]/) {
  329.         # If this option is a numeric option, make its flag be & and
  330.         # its option string flag position be the position of & in
  331.         # the option string.
  332.         Option = "&"
  333.         Pos = NumOpt
  334.         # Prefix Arg with a char so that ArgInd will point to the
  335.         # first char of the numeric option.
  336.         Arg = "&" Arg
  337.         ArgLen++
  338.         }
  339.         # Find position of flag in option string, to get its type (if any).
  340.         # Disallow & as literal flag.
  341.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  342.         if (AllowUnrecOpt) {
  343.             Escape = 1
  344.             break
  345.         }
  346.         else {
  347.             OptErr = "Invalid option: " specGiven Option
  348.             return -3
  349.         }
  350.         }
  351.  
  352.         # Find what the value of the option will be if it takes one.
  353.         # NeedNextOpt is true if the option specifier is the last char of
  354.         # this arg, which means that if the option requires a value it is
  355.         # the next arg.
  356.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  357.         if (GotValue = ArgNum + 1 < argc)
  358.             Value = argv[ArgNum+1]
  359.         }
  360.         else {    # Value is included with option
  361.         Value = substr(Arg,ArgInd + 1)
  362.         GotValue = 1
  363.         }
  364.  
  365.         if (HadValue = AssignVal(Option,Value,Options,
  366.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  367.         specGiven)) {
  368.         if (HadValue < 0)    # error occured
  369.             return HadValue
  370.         if (HadValue == 2)
  371.             ArgInd++    # Account for the single-char value we used.
  372.         else {
  373.             if (NeedNextOpt) {    # option took next arg as value
  374.             delete argv[++ArgNum]
  375.             ArgsLeft--
  376.             }
  377.             break    # This option has been used up
  378.         }
  379.         }
  380.     }
  381.     if (Escape)
  382.         break
  383.     # Do not delete arg until after processing of it, so that if it is not
  384.     # recognized it can be left in ARGV[].
  385.     delete argv[ArgNum]
  386.     ArgsLeft--
  387.     }
  388.     if (compress != 0) {
  389.     dest = 1
  390.     src = argc - ArgsLeft + 1
  391.     for (count = ArgsLeft - 1; count; count--) {
  392.         ARGV[dest] = ARGV[src]
  393.         dest++
  394.         src++
  395.     }
  396.     }
  397.     return ArgsLeft
  398. }
  399.  
  400. # Assignment to values in Options[] occurs only in this function.
  401. # Option: Option specifier character.
  402. # Value: Value to be assigned to option, if it takes a value.
  403. # Options[]: Options array to return values in.
  404. # ArgType: Argument type specifier character.
  405. # GotValue: Whether any value is available to be assigned to this option.
  406. # Name: Name of option being processed.
  407. # OptionNum: Number of this option (starting with 1) if set in argv[],
  408. #     or 0 if it was given in a config file or in the environment.
  409. # SingleOpt: true if the value (if any) that is available for this option was
  410. #     given as part of the same command line arg as the option.  Used only for
  411. #     options from the command line.
  412. # specGiven is the option specifier character use, if any (e.g. - or +),
  413. # for use in error messages.
  414. # Global variables: OptErr
  415. # Return value: negative value on error, 0 if option did not require an
  416. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  417. # the arg.
  418. # Current error values:
  419. # -1: Option that required an argument did not get it.
  420. # -2: Value of incorrect type supplied for option.
  421. # -3: Bad type given for option &
  422. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  423. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  424.     # If option takes a value...    [
  425.     NumTypes = "*()#<>]"
  426.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  427.     OptErr = "Bad type given for & option"
  428.     return -3
  429.     }
  430.  
  431.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  432.     if (!GotValue) {
  433.         if (Name != "")
  434.         OptErr = "Variable requires a value -- " Name
  435.         else
  436.         OptErr = "option requires an argument -- " Option
  437.         return -1
  438.     }
  439.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  440.         OptErr = Err
  441.         return -2
  442.     }
  443.     # Mark this as a numeric variable; will be propogated to Options[] val.
  444.     if (ArgType != ":" && ArgType != ";")
  445.         Value += 0
  446.     if ((Instance = ++Options[Option,"count"]) > 1)
  447.         Options[Option,Instance] = Value
  448.     else
  449.         Options[Option] = Value
  450.     }
  451.     # If this is an environ or rcfile assignment & it was given a value...
  452.     else if (!OptionNum && Value != "") {
  453.     UsedValue = 1
  454.     # If the value is "0" or "-" and this is the first instance of it,
  455.     # do not set Options[Option]; this allows an assignment in an rcfile to
  456.     # turn off an option (for the simple "Option in Options" test) in such
  457.     # a way that it cannot be turned on in a later file.
  458.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  459.         Instance = 1
  460.     else
  461.         Instance = ++Options[Option]
  462.     # Save the value even though this is a flag
  463.     Options[Option,Instance] = Value
  464.     }
  465.     # If this is a command line flag and has a - following it in the same arg,
  466.     # it is being turned off.
  467.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  468.     UsedValue = 2
  469.     if (Option in Options)
  470.         Instance = ++Options[Option]
  471.     else
  472.         Instance = 1
  473.     Options[Option,Instance]
  474.     }
  475.     # If this is a flag assignment without a value, increment the count for the
  476.     # flag unless it was turned off.  The indicator for a flag being turned off
  477.     # is that the flag index has not been set in Options[] but it has an
  478.     # instance count.
  479.     else if (Option in Options || !((Option,1) in Options))
  480.     # Increment number of times this flag seen; will inc null value to 1
  481.     Instance = ++Options[Option]
  482.     Options[Option,"num",Instance] = OptionNum
  483.     return UsedValue
  484. }
  485.  
  486. # Option is the option letter
  487. # Value is the value being assigned
  488. # Name is the var name of the option, if any
  489. # ArgType is one of:
  490. # :    String argument
  491. # ;    Non-null string argument
  492. # *    Floating point argument
  493. # (    Non-negative floating point argument
  494. # )    Positive floating point argument
  495. # #    Integer argument
  496. # <    Non-negative integer argument
  497. # >    Positive integer argument
  498. # specGiven is the option specifier character use, if any (e.g. - or +),
  499. # for use in error messages.
  500. # Returns null on success, err string on error
  501. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  502.     if (ArgType == ":")
  503.     return ""
  504.     if (ArgType == ";") {
  505.     if (Value == "")
  506.         Err = "must be a non-empty string"
  507.     }
  508.     # A number begins with optional + or -, and is followed by a string of
  509.     # digits or a decimal with digits before it, after it, or both
  510.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  511.     Err = "must be a number"
  512.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  513.     Err = "may not include a fraction"
  514.     else if (ArgType ~ "[()<>]" && Value < 0)
  515.     Err = "may not be negative"
  516.     # (
  517.     else if (ArgType ~ "[)>]" && Value == 0)
  518.     Err = "must be a positive number"
  519.     if (Err != "") {
  520.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  521.     if (Name != "")
  522.         return ErrStr "variable " substr(Name,1,1) " " Err
  523.     else {
  524.         if (Option == "&")
  525.         Option = Value
  526.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  527.     }
  528.     }
  529.     else
  530.     return ""
  531. }
  532.  
  533. # Note: only the above functions are needed by ProcArgs.
  534. # The rest of these functions call ProcArgs() and also do other
  535. # option-processing stuff.
  536.  
  537. # Opts: Process command line arguments.
  538. # Opts processes command line arguments using ProcArgs()
  539. # and checks for errors.  If an error occurs, a message is printed
  540. # and the program is exited.
  541. #
  542. # Input variables:
  543. # Name is the name of the program, for error messages.
  544. # Usage is a usage message, for error messages.
  545. # OptList the option description string, as used by ProcArgs().
  546. # MinArgs is the minimum number of non-option arguments that this
  547. # program should have, non including ARGV[0] and +h.
  548. # If the program does not require any non-option arguments,
  549. # MinArgs should be omitted or given as 0.
  550. # rcFiles, if given, is a colon-seprated list of filenames to read for
  551. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  552. # by the value of the environment variable HOME.  If a filename begins with
  553. # $, the part from the character after the $ up until (but not including)
  554. # the first character not in [a-zA-Z0-9_] will be searched for in the
  555. # environment; if found its value will be substituted, if not the filename will
  556. # be discarded.
  557. # rcfiles are read in the order given.
  558. # Values given in them will not override values given on the command line,
  559. # and values given in later files will not override those set in earlier
  560. # files, because AssignVal() will store each with a different instance index.
  561. # The first instance of each variable, either on the command line or in an
  562. # rcfile, will be stored with no instance index, and this is the value
  563. # normally used by programs that call this function.
  564. # VarNames is a comma-separated list of variable names to map to options,
  565. # in the same order as the options are given in OptList.
  566. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  567. # searched for in the environment.  If set to -1, all values will be searched
  568. # for in the environment.  Values given in the environment will override
  569. # those given in the rcfiles but not those given on the command line.
  570. # NoRCopt, if given, is an additional letter option that if given on the
  571. # command line prevents the rcfiles from being read.
  572. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  573. # ExclusiveOptions() for a description of exOpts.
  574. # Special options:
  575. # If x is made an option and is given, some debugging info is output.
  576. # h is assumed to be the help option.
  577.  
  578. # Global variables:
  579. # The command line arguments are taken from ARGV[].
  580. # The arguments that are option specifiers and values are removed from
  581. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  582. # The number of elements in ARGV[] should be in ARGC.
  583. # After processing, ARGC is set to the number of elements left in ARGV[].
  584. # The option values are put in Options[].
  585. # On error, Err is set to a positive integer value so it can be checked for in
  586. # an END block.
  587. # Return value: The number of elements left in ARGV is returned.
  588. # Must keep OptErr global since it may be set by InitOpts().
  589. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  590. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  591.     if (MinArgs == "")
  592.     MinArgs = 0
  593.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  594.     optChars)
  595.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  596.     if (ArgsLeft >= 0) {
  597.         OptErr = "Not enough arguments"
  598.         Err = 4
  599.     }
  600.     else
  601.         Err = -ArgsLeft
  602.     printf "%s: %s.\nUse -h for help.\n%s\n",
  603.     Name,OptErr,Usage > "/dev/stderr"
  604.     exit 1
  605.     }
  606.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  607.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  608.     {
  609.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  610.     Err = -e
  611.     exit 1
  612.     }
  613.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  614.     {
  615.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  616.     Err = 1
  617.     exit 1
  618.     }
  619.     return ArgsLeft
  620. }
  621.  
  622. # ReadConfFile(): Read a file containing var/value assignments, in the form
  623. # <variable-name><assignment-char><value>.
  624. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  625. # line and whitespace between the variable name and the assignment character) 
  626. # is stripped.  Lines that do not contain an assignment operator or which
  627. # contain a null variable name are ignored, other than possibly being noted in
  628. # the return value.  If more than one assignment is made to a variable, the
  629. # first assignment is used.
  630. # Input variables:
  631. # File is the file to read.
  632. # Comment is the line-comment character.  If it is found as the first non-
  633. #     whitespace character on a line, the line is ignored.
  634. # Assign is the assignment string.  The first instance of Assign on a line
  635. #     separates the variable name from its value.
  636. # If StripWhite is true, whitespace around the value (whitespace between the
  637. #     assignment char and trailing whitespace on the line) is stripped.
  638. # VarPat is a pattern that variable names must match.  
  639. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  640. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  641. #     a line; no assignment operator is needed.  These variables are set in
  642. #     the output array with a null value.  Lines containing nothing but
  643. #     whitespace are still ignored.
  644. # Output variables:
  645. # Values[] contains the assignments, with the indexes being the variable names
  646. #     and the values being the assigned values.
  647. # Lines[] contains the line number that each variable occured on.  A flag set
  648. #     is record by giving it an index in Lines[] but not in Values[].
  649. # Return value:
  650. # If any errors occur, a string consisting of descriptions of the errors
  651. # separated by newlines is returned.  In no case will the string start with a
  652. # numeric value.  If no errors occur,  the number of lines read is returned.
  653. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  654. FlagsOK,
  655. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  656.     if (Comment != "")
  657.     Comment = "^" Comment
  658.     AssignLen = length(Assign)
  659.     if (VarPat == "")
  660.     VarPat = "."    # null varname not allowed
  661.     while ((Status = (getline Line < File)) == 1) {
  662.     LineNum++
  663.     sub("^[ \t]+","",Line)
  664.     if (Line == "")        # blank line
  665.         continue
  666.     if (Comment != "" && Line ~ Comment)
  667.         continue
  668.     if (Pos = index(Line,Assign)) {
  669.         Var = substr(Line,1,Pos-1)
  670.         Val = substr(Line,Pos+AssignLen)
  671.         if (StripWhite) {
  672.         sub("^[ \t]+","",Val)
  673.         sub("[ \t]+$","",Val)
  674.         }
  675.     }
  676.     else {
  677.         Var = Line    # If no value, var is entire line
  678.         Val = ""
  679.     }
  680.     if (!FlagsOK && Val == "") {
  681.         Errs = Errs \
  682.         sprintf("\nBad assignment on line %d of file %s: %s",
  683.         LineNum,File,Line)
  684.         continue
  685.     }
  686.     sub("[ \t]+$","",Var)
  687.     if (Var !~ VarPat) {
  688.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  689.         LineNum,File,Var)
  690.         continue
  691.     }
  692.     if (!(Var in Lines)) {
  693.         Lines[Var] = LineNum
  694.         if (Pos)
  695.         Values[Var] = Val
  696.     }
  697.     }
  698.     if (Status)
  699.     Errs = Errs "\nCould not read file " File
  700.     close(File)
  701.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  702. }
  703.  
  704. # Variables:
  705. # Data is stored in Options[].
  706. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  707. # Global vars:
  708. # Sets OptErr.  Uses ENVIRON[].
  709. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  710. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  711. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  712. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  713.     split("",filesRead,"")    # make awk know this is an array
  714.     NumVars = split(VarNames,Vars,",")
  715.     TypesInd = Ret = 0
  716.     if (EnvSearch == -1)
  717.     EnvSearch = NumVars
  718.     for (i = 1; i <= NumVars; i++) {
  719.     Var = Vars[i]
  720.     CharOpt = substr(OptList,++TypesInd,1)
  721.     if (CharOpt ~ "^[:;*()#<>&]$")
  722.         CharOpt = substr(OptList,++TypesInd,1)
  723.     Map[Var] = CharOpt
  724.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  725.     # Do not overwrite entries from environment
  726.     if (i <= EnvSearch && Var in ENVIRON &&
  727.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  728.         return Err
  729.     }
  730.  
  731.     numrcFiles = split(rcFiles,fNames,":")
  732.     for (i = 1; i <= numrcFiles; i++) {
  733.     rcFile = fNames[i]
  734.     if (rcFile ~ "^~/")
  735.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  736.     else if (rcFile ~ /^\$/) {
  737.         rcFile = substr(rcFile,2)
  738.         match(rcFile,"^[a-zA-Z0-9_]*")
  739.         envvar = substr(rcFile,1,RLENGTH)
  740.         if (envvar in ENVIRON)
  741.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  742.         else
  743.         continue
  744.     }
  745.     if (rcFile in filesRead)
  746.         continue
  747.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  748.     # may be the same
  749.     filesRead[rcFile]
  750.     if ("x" in Options)
  751.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  752.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  753.     if (retStr > 0)
  754.         READ_RCFILE = 1
  755.     else if (ret != "") {
  756.         OptErr = retStr
  757.         Ret = -1
  758.     }
  759.     for (Var in Lines)
  760.         if (Var in Map) {
  761.         if ((Err = AssignVal(Map[Var],
  762.         Var in Values ? Values[Var] : "",Options,Types[Var],
  763.         Var in Values,Var,0)) < 0)
  764.             return Err
  765.         }
  766.         else {
  767.         OptErr = sprintf(\
  768.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  769.         Lines[Var],rcFile)
  770.         Ret = -1
  771.         }
  772.     }
  773.  
  774.     if ("x" in Options)
  775.     for (Var in Map)
  776.         if (Map[Var] in Options)
  777.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  778.         "/dev/stderr"
  779.         else
  780.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  781.     return Ret
  782. }
  783.  
  784. # OptSets is a semicolon-separated list of sets of option sets.
  785. # Within a list of option sets, the option sets are separated by commas.  For
  786. # each set of sets, if any option in one of the sets is in Options[] AND any
  787. # option in one of the other sets is in Options[], an error string is returned.
  788. # If no conflicts are found, nothing is returned.
  789. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  790. # the exclusions presented by the first set of sets (ab,def,g) if:
  791. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  792. # (a or b is in Options[]) AND (g is in Options) OR
  793. # (d, e, or f is in Options[]) AND (g is in Options)
  794. # An error will be returned due to the exclusions presented by the second set
  795. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  796. # todo: make options given on command line unset options given in config file
  797. # todo: that they conflict with.
  798. function ExclusiveOptions(OptSets,Options,
  799. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  800. SetNum,OSetNum) {
  801.     NumSetSets = split(OptSets,SetSets,";")
  802.     # For each set of sets...
  803.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  804.     # NumSets is the number of sets in this set of sets.
  805.     NumSets = split(SetSets[SetSet],Sets,",")
  806.     # For each set in a set of sets except the last...
  807.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  808.         s1 = Sets[SetNum]
  809.         L1 = length(s1)
  810.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  811.         # If any of the options in this set was given, check whether
  812.         # any of the options in the other sets was given.  Only check
  813.         # later sets since earlier sets will have already been checked
  814.         # against this set.
  815.         if ((c1 = substr(s1,Pos1,1)) in Options)
  816.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  817.             s2 = Sets[OSetNum]
  818.             L2 = length(s2)
  819.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  820.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  821.                 ErrStr = ErrStr "\n"\
  822.                 sprintf("Cannot give both %s and %s options.",
  823.                 c1,c2)
  824.             }
  825.     }
  826.     }
  827.     if (ErrStr != "")
  828.     return substr(ErrStr,2)
  829.     return ""
  830. }
  831.  
  832. # The value of each instance of option Opt that occurs in Options[] is made an
  833. # index of Set[].
  834. # The return value is the number of instances of Opt in Options.
  835. function Opt2Set(Options,Opt,Set,  count) {
  836.     if (!(Opt in Options))
  837.     return 0
  838.     Set[Options[Opt]]
  839.     count = Options[Opt,"count"]
  840.     for (; count > 1; count--)
  841.     Set[Options[Opt,count]]
  842.     return count
  843. }
  844.  
  845. # The value of each instance of option Opt that occurs in Options[] that
  846. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  847. # Other values are made indexes of Set[].
  848. # The return value is the number of instances of Opt in Options.
  849. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  850.     ret = Opt2Set(Options,Opt,aSet)
  851.     for (value in aSet)
  852.     if (substr(value,1,1) == "!")
  853.         nSet[substr(value,2)]
  854.     else
  855.         Set[value]
  856.     return ret
  857. }
  858.  
  859. # Returns true if option Opt was given on the command line.
  860. function CmdLineOpt(Options,Opt,  i) {
  861.     for (i = 1; (Opt,"num",i) in Options; i++)
  862.     if (Options[Opt,"num",i] != 0)
  863.         return 1
  864.     return 0
  865. }
  866. ### End of ProcArgs library
  867. ### Start of tinfo lib
  868. # @(#) tinfo 1.0 96/11/30
  869. # altInit(): Get alternate character set terminfo capabilities.
  870. # term, noerror: see tiget().
  871. # tinfo: contains the acsc capability, and any of the enacs, smacs, and rmacs
  872. # capabilities that are defined for the terminal.  Each is indexed by its
  873. # capability name.  enacs is used to enable the alternate character set;
  874. # smacs starts it; rmacs ends it.  acsc is the mapping of vt100 alternate
  875. # character codes to those appropriate for the given terminal.
  876. # AltMap is the acsc string broken down with each alternate character indexed
  877. # by its vt100 equivalent.  num is an ordered list of the vt100 characters
  878. # indexed starting with 1, for applications that need to know what order they
  879. # were given in.
  880. # The global _macs[] is set up with _macs[0] = rmacs & _macs[1] = smacs, for
  881. # use by altPrint().
  882. # The alternate characters and their indexes (vt100 equivalents) are:
  883. # 0  solid square block        a  checker board    f  degree symbol
  884. # g  plus/minus            h  board of squares    j  lower right corner
  885. # k  upper right corner        l  upper left corner    m  lower left corner
  886. # n  plus            q  horizontal line    t  left tee
  887. # u  right tee            v  bottom tee        w  top tee
  888. # x  vertical line        +  arrow pointing right    .  arrow pointing down
  889. # -  arrow pointing up        ,  arrow pointing left    `  diamond
  890. # ~  bullet            I  lantern symbol    o  scan line 1
  891. # s  scan line 9
  892. function altInit(tinfo,term,noerror,AltMap,num,  ret,caplist,acsc,len,j,i) {
  893.     if (ret = tiget("acsc",tinfo,term)) {
  894.     # All other types of errors cause tput to print an informative message
  895.     # to stderr, which is not redirected.
  896.     if (!noerror && ret == 1)
  897.         print "Terminal has no acsc capability." > "/dev/stderr"
  898.     return ret
  899.     }
  900.     caplist = "enacs,smacs,rmacs"
  901.     tiget(caplist,tinfo,term)
  902.     acsc = tinfo["acsc"]
  903.     len = length(acsc)
  904.     j = 0
  905.     for (i = 1; i < len; i += 2)
  906.     AltMap[num[++j] = substr(acsc,i,1)] = substr(acsc,i+1,1)
  907.     if ("rmacs" in tinfo)
  908.     _macs[0] = tinfo["rmacs"]
  909.     if ("smacs" in tinfo)
  910.     _macs[1] = tinfo["smacs"]
  911. }
  912.  
  913. # altPrint: Print characters in either the alternate or standard character set.
  914. # string is the string to print.
  915. # alt should be 1 if string is in the alternate character set; 0 if in the
  916. # standard character set.
  917. # tinfo contains the smacs and rmacs strings, if needed.
  918. # altPrint keeps track of whether the terminal is in the standard or alternate
  919. # character set, and issues smacs and rmacs as needed.
  920. # It should always be called with alt false at the end of program execution to
  921. # ensure that the terminal is left in the standard character set.
  922. # Globals: The character set is tracked in _altPrintSet
  923. function altPrint(string,alt,tinfo) {
  924.     if (alt != _altPrintSet) {
  925.     printf "%s%s",_macs[alt],string
  926.     _altPrintSet = alt
  927.     }
  928.     else
  929.     printf "%s",string
  930. }
  931.  
  932. # tiget: get terminfo capabilities.
  933. # capnames is a comma-separated list of terminfo capabilities to get.
  934. # Each capability is put in tinfo[], indexed by capability name.
  935. # If term is passed, it is the terminal type to get the capabilities for.
  936. # If not, the value of the environment variable TERM is used.
  937. # If noerror is true, error messages are suppressed.
  938. # Return value: the exit status of the last tput, or -1 if term is not passed
  939. # and there is no TERM environment variable.
  940. function tiget(capnames,tinfo,term,noerror,  cmd,RS,ret,names,capname,i) {
  941.     if (term == "")
  942.     if ("TERM" in ENVIRON)
  943.         term = ENVIRON["TERM"]
  944.     else
  945.         return -1
  946.     split(capnames,names,",")
  947.     RS = ""    # this makes the record separator be "\n\n", which hopefully
  948.         # is not very common in terminfo capabilities
  949.     for (i = 1; i in names; i++) {
  950.     capname = names[i]
  951.     cmd = "exec tput -T " term " " capname
  952.     if (noerror)
  953.         cmd = cmd " 2>/dev/null"
  954.     cmd | getline
  955.     if (!(ret = close(cmd)))
  956.         # printf interprets many of the escape chars in the same manner that
  957.         # the terminfo library does... not perfect, but better than nothing
  958.         tinfo[capname] = sprintf($0)
  959.     }
  960.     return ret
  961. }
  962.  
  963. function tiget1(capname,term,noerror,  capnames) {
  964.     delete tinfo[capname]
  965.     tiget(capname,tinfo,term,noerror)
  966.     return tinfo[capname]
  967. }
  968. ### End of tinfo lib
  969. ### Start of DrawTrees lib
  970. # @(#) DrawTrees 1.0 96/11/30
  971. #      Data[] is a tree of data to draw.  The indexes consist of one or more
  972. # integer values separated by SUBSEP.  The "depth" of the element determines
  973. # how many integers (dimensions) are contained in the index.  For each set
  974. # of node siblings, the integer describing the varying dimension varies from
  975. # 1 through n where n is the number of siblings.  This shows the indexes
  976. # used for the elements of a small tree with depth 3:
  977. # 1----+-1,1--+-1,1,1
  978. #      |      |-1,2,2
  979. #      |      \-1,2,3
  980. #      \-1,2--+-1,2,1
  981. #             \-1,2,2
  982. # 2------2,1--+-2,1,1
  983. #             \-2,2,2
  984. #       ^----^--see below
  985. #      The values of the elements are lines of data which constitute the
  986. # nodes of the tree.
  987. #      By default, the tree is drawn with each node on a separate line. 
  988. # Offset is the horizonal offset of each child from its parent.  It must be
  989. # at least 1.  If Width is non-0, the tree is drawn with the first child of
  990. # each parent immediately to the right of its parent.  Width is the number
  991. # of characters allocated to the node data for each level.  If the data for
  992. # an interior node is longer than Width, the value is truncated to Width-1
  993. # characters and a left-tee is appended to indicate the truncation, so Width
  994. # should be at least two.  If this style is used, Offset is the number of
  995. # characters of additional horizontal separation to use after the "split
  996. # point"; in the example tree above, Width is set to 1, causing the addition
  997. # of the characters at the positions marked by ^ on the "see below" line.
  998. #      The tree is drawn using box-drawing characters appropriate to the
  999. # terminal if they are available, and a default set of ASCII characters if
  1000. # not.
  1001. # If AltChars[] contains all of the following elements, they are used to draw
  1002. # the tree.  I is the index to use; A is the ASCII default.
  1003. # I A Description
  1004. # x | Vertical bar
  1005. # q - Horizontal bar
  1006. # m \ bottom left corner
  1007. # w + Top tee 
  1008. # t } Left tee
  1009. # + > Right arrow (optional)
  1010. # ~ * Bullet (optional)
  1011. # If AltChars[] does not contain all of these elements and the alternate
  1012. # character set it used, AltChars[] is returned filled in with the
  1013. # characters used to draw the tree.  The same array can then be passed back
  1014. # to DrawTrees(), avoiding the need for it to use tput again to get the
  1015. # terminal's alternate character set capabilities.
  1016. # If Spaces is true, indentation is done with spaces only; the effect is to
  1017. # set all of the above characters to be a space.
  1018. # If term is passed, it overrides the TERM environment variable.  Pass "dumb"
  1019. # to force the ASCII values to be used.  
  1020. # If the terminal has a right-arrow character defined and useArrow is true,
  1021. # it is used for the branch character to the left of node data.
  1022. # If maxLength is non-0, output lines are truncated to maxLength characters.
  1023. # If AddInd is true, in the output each value is preceded by its index.
  1024. # If Sort is true, the tree is sorted by the lexicographical values of its
  1025. # elements, and the qsort library must be included in the program.
  1026. function DrawTrees(Data,Offset,Width,AltChars,Spaces,term,useArrow,maxLength,
  1027. AddInd,Sort,
  1028. i,tinfo,Strings,smacs,rmacs,BranchIndent,BlankIndent,bTail,veBar,hoBar,bLeft,
  1029. topTee,lTee,arrow,bullet,WidthBar,OffsetBar) {
  1030.     if (Spaces) {
  1031.     veBar = hoBar = bLeft = topTee = lTee = arrow = " "
  1032.     bullet = "*"
  1033.     }
  1034.     else {
  1035.     if ("x" in AltChars && "q" in AltChars && "m" in AltChars && \
  1036.     "w" in AltChars && "t" in AltChars) {
  1037.         tinfo["smacs"] = AltChars["smacs"]
  1038.         tinfo["rmacs"] = AltChars["rmacs"]
  1039.         if ("enacs" in AltChars)
  1040.         tinfo["enacs"] = AltChars["enacs"]
  1041.     }
  1042.     else
  1043.         altInit(tinfo,term,1,AltChars)
  1044.     if ("x" in AltChars && "q" in AltChars && "m" in AltChars && \
  1045.     "w" in AltChars && "t" in AltChars) {
  1046.         AltChars["smacs"] = smacs = Strings["smacs"] = tinfo["smacs"]
  1047.         AltChars["rmacs"] = rmacs = Strings["rmacs"] = tinfo["rmacs"]
  1048.         if ("enacs" in tinfo) {
  1049.         printf "%s",tinfo["enacs"]
  1050.         AltChars["enacs"] = tinfo["enacs"]
  1051.         }
  1052.         veBar = AltChars["x"]
  1053.         hoBar = AltChars["q"]
  1054.         bLeft = AltChars["m"]
  1055.         topTee = AltChars["w"]
  1056.         lTee = AltChars["t"]
  1057.         arrow = "+" in AltChars ? AltChars["+"] : hoBar
  1058.         bullet = "~" in AltChars ? AltChars["~"] : lTee
  1059.     }
  1060.     else {
  1061.         # Do not attempt mixing of alt & regular char sets for tree drawing
  1062.         veBar = "|"
  1063.         hoBar = "-"
  1064.         bLeft = "\\"
  1065.         topTee = "+"        # {
  1066.         lTee = "}"
  1067.         arrow = ">"
  1068.         bullet = "*"
  1069.     }
  1070.     }
  1071.     # b: blank indent.  Preceded by newline, followed by branch char.
  1072.     # v: indent that includes a vertical branch on the left:  "|    "
  1073.     #    Preceded by newline or whitespace; followed by branch char.
  1074.     # l: lower left horizontal branch indent.                 "\--->"
  1075.     #    Preceded by newline or whitespace; followed by node data.
  1076.     # t: left tee horizontal branch indent.                   "}--->"
  1077.     #    Preceded by newline or whitespace; followed by node data.
  1078.     # p: Node padding.  Must be adjusted to fit, so is not
  1079.     #    surrounded by smacs/rmacs.  Preceded by node data; followed by branch.
  1080.     # n: Internode branch.  Preceded by branch; followed by node data.  "-->"
  1081.     # tn: Teed internode branch.  Preceded b/branch; followed b/node data."+->"
  1082.     # c: Truncation character.  Followed by branch.
  1083.     # lt: Line truncation character.
  1084.     for (i = Offset + Width; i > 0; i-=1) {
  1085.     BlankIndent = BlankIndent " "
  1086.     BranchIndent = BranchIndent hoBar
  1087.     }
  1088.     WidthIndent = substr(BlankIndent,1,Width)
  1089.     OffsetIndent = substr(BranchIndent,1,Offset)
  1090.     if (BranchIndent != "")
  1091.     bTail = useArrow ? arrow : hoBar
  1092.     Strings["c"] = smacs lTee
  1093.     Strings["lt"] = smacs bullet rmacs
  1094.     Strings["p"] = BranchIndent
  1095.     Strings["n"] = substr(BranchIndent,1,Offset-1) bTail rmacs
  1096.     Strings["tn"] = topTee substr(BranchIndent,1,Offset-2) bTail rmacs
  1097.  
  1098.     Strings["b"] = BlankIndent
  1099.     Strings["v"] = WidthIndent smacs veBar rmacs substr(BlankIndent,1,Offset-1)
  1100.     Strings["l"] = WidthIndent smacs bLeft substr(OffsetIndent,3) bTail rmacs
  1101.     Strings["t"] = WidthIndent smacs lTee  substr(OffsetIndent,3) bTail rmacs
  1102.  
  1103.     dtTraverse(Data,"",Strings,0,"",Width,maxLength,Offset+Width,AddInd,Sort)
  1104. }
  1105.  
  1106. # dtTraverse(): Traverse and print a subtree.
  1107. # Data: as described for DrawTrees().
  1108. # catind: index into Data[] for the parent of this node, followed by a SUBSEP
  1109. #    char.
  1110. # level: The depth of this node, with tree roots at level 0.
  1111. # branch: An indentation string to print the vertical components of the
  1112. #    branches of the siblings of the parents of this node.
  1113. # Return value: 1 if 
  1114. function dtTraverse(Data,catind,Strings,level,branch,Width,Length,levelWidth,
  1115. AddInd,Sort,
  1116. i,ind,siblings,children,nbranch,len,s,subLength,value,k,Arr) {
  1117.     if (Length && (subLength = Length - levelWidth) < 1)
  1118.     # Make sure subLength does not end up 0, which indicates no limit
  1119.     subLength = -1
  1120.     if (Sort) {
  1121.     # build a subtree level to sort
  1122.     for (i = 1; (ind = catind i) in Data; i++)
  1123.         Arr[ind] = Data[ind]
  1124.     qsortArbIndByValue(Arr,k)
  1125.     }
  1126.     for (i = 1; (ind = catind i) in Data; i++) {
  1127.     if (level) {    # Draw indentation string
  1128.         siblings = (catind (i+1)) in Data
  1129.         if (!Width || i != 1)
  1130.         # If parent has not already drawn indent string
  1131.         printf "%s",branch Strings[siblings ? "t" : "l"]
  1132.     }
  1133.     if (Sort)
  1134.         ind = k[i]
  1135.     children = (ind,1) in Data
  1136.     # Print node data
  1137.     value = Data[ind]
  1138.     if (AddInd)
  1139.         value = ind ":" value
  1140.     if (Width && children) {
  1141.         if (subLength == -1) # Won't be able to show children; indicate
  1142.         printf "%.*s%s\n",Length-1,value,Strings["lt"]
  1143.         else {
  1144.         if ((len = length(value)) > Width)
  1145.             printf "%.*s%s",Width-1,value,Strings["c"] # truncate
  1146.         else
  1147.             printf "%s%s%.*s",value,Strings["smacs"], Width-len,
  1148.             Strings["p"]    # pad on right
  1149.         # If this node has children, print offset branch
  1150.         printf "%s",Strings[((ind,2) in Data) ? "tn" : "n"]
  1151.         }
  1152.     }
  1153.     else if (Length) {
  1154.         if (length(value) > Length)
  1155.         printf "%.*s%s\n",Length-1,value,Strings["lt"]
  1156.         else
  1157.         printf "%.*s\n",Length,value
  1158.     }
  1159.     else
  1160.         print value
  1161.     if (children && subLength != -1) {
  1162.         if (level)
  1163.         nbranch = branch Strings[siblings ? "v" : "b"]
  1164.         dtTraverse(Data,ind SUBSEP,Strings,level+1,nbranch,Width,subLength,
  1165.         levelWidth,AddInd,Sort)
  1166.     }
  1167.     }
  1168. }
  1169.  
  1170. # buildTree: add nodes to a tree, find each of their children, and call
  1171. # buildTree() recursively for each child set.
  1172. # Tree[] is the tree being built, in the style described for DrawTrees().
  1173. # treeData[1..n] contains data that should be added to Tree[] (a string may
  1174. # modified by getChildren() if it is called for a node).
  1175. # Prefix is the string that the index of each element in treeData[] should be
  1176. # prefixed with when it is copied to Tree[].
  1177. # Depth is the current depth within the tree, with the top node at depth 1.
  1178. # It is used only to be passed to getChildren() in case it cares.
  1179. # childData[1..n] has two purposes.  buildTree() will only call getChildren()
  1180. # for those indexes of treeData[] that also exist in childData[].  In addition,
  1181. # additional data may be passed to getChildren() for a node by assigning a
  1182. # value to the node index in childData[].
  1183. #
  1184. # For each element in childData[], the function getChildren() is called with
  1185. # the parameters (treeData,childData[i],cTreeData,cChildData,i,Depth).
  1186. # cTreeData[] and cChildData[] are arrays which should be filled in the node
  1187. # has any children.
  1188. # The return value of getChildren() should be the number of children found.
  1189. # treeData[] is passed rather than the value of one of its elements so that
  1190. # the value of the element being processed may be modified before it is
  1191. # copied to Tree[].  If it is deleted from the array, it is skipped (not
  1192. # copied to Tree[]); in this case no children should be added.
  1193. # getChildren() must be defined elsewhere in the program.
  1194. function buildTree(Tree,treeData,childData,Prefix,Depth,
  1195. i,cTreeData,cChildData,j) {
  1196.     j = 1
  1197.     for (i = 1; i in treeData; i++) {
  1198.     split("",cTreeData)
  1199.     split("",cChildData)
  1200.     if (i in childData && \
  1201.     getChildren(treeData,childData[i],cTreeData,cChildData,i,Depth) && \
  1202.     i in treeData)
  1203.         buildTree(Tree,cTreeData,cChildData,Prefix j SUBSEP,Depth+1)
  1204.     if (i in treeData)
  1205.         Tree[Prefix j++] = treeData[i]
  1206.     }
  1207. }
  1208.  
  1209. # Breadth-first-search version of buildTree().  This is intended to flatten
  1210. # the tree representation of a possibly cyclic graph as much as possible.
  1211. # All nodes at each depth are visited before the nodes at the next depth are
  1212. # visited.
  1213. # All parameters are as for buildTree() except that the scalar Prefix is
  1214. # replaced by the array Prefixes[].  It has an element for each value in
  1215. # treeData[] (with the same index), with the value being the prefix for the
  1216. # index which that element should be stored in treeData[] with.
  1217. # getChildren() is called as by buildTree(), except that there is an additional
  1218. # argument telling getChildren() the first index in cTreeData[] and
  1219. # cChildData[] to use (instead of starting at 1).
  1220. function bfBuildTree(Tree,treeData,childData,Prefixes,Depth,
  1221. i,cTreeData,cChildData,j,childPos,cPrefixes,nChild,cIndex,l) {
  1222.     childPos = 1
  1223.     for (i = 1; i in treeData; i++) {
  1224.     nChild = (i in childData) ? \
  1225.     getChildren(treeData,childData[i],cTreeData,cChildData,i,Depth,
  1226.     childPos) : 0
  1227.     if (i in treeData) {    # if not skipping this node
  1228.         if (i == 1 || Prefixes[i] != Prefixes[i-1])
  1229.         j = 1
  1230.         cIndex = Prefixes[i] j SUBSEP
  1231.         for (l = 1; l <= nChild; l++)
  1232.         cPrefixes[childPos++] = cIndex
  1233.         Tree[Prefixes[i] j] = treeData[i]
  1234.         j++
  1235.     }
  1236.     }
  1237.     if (childPos > 1)
  1238.     bfBuildTree(Tree,cTreeData,cChildData,cPrefixes,Depth+1)
  1239. }
  1240. ### End of DrawTrees lib
  1241.